/**
 * Copyright 2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.fos.kodo;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.jianggujin.fos.JBucket;
import com.jianggujin.fos.JClient;
import com.jianggujin.fos.JFOSException;
import com.jianggujin.fos.JListObjectsRequest;
import com.jianggujin.fos.JObject;
import com.jianggujin.fos.JObjectListing;
import com.jianggujin.fos.JObjectMetadata;
import com.jianggujin.fos.JObjectSummary;
import com.jianggujin.fos.util.JObjectUtils;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.BatchStatus;
import com.qiniu.storage.model.FileListing;
import com.qiniu.util.Auth;

import lombok.Getter;
import lombok.NonNull;

/**
 * 七牛云客户端
 * 
 * @author jianggujin
 *
 */
@Getter
public class JKodoClient implements JClient {
    private JKodoConfiguration configuration;
    private Auth auth;
    private BucketManager bucketManager;
    private UploadManager uploadManager;

    public JKodoClient(@NonNull JKodoConfiguration configuration) {
        this.configuration = configuration;
        auth = Auth.create(configuration.getAccessKey(), configuration.getSecretKey());
        Configuration cfg = new Configuration(configuration.getRegion().getKodoRegion());
        JObjectUtils.copyProperties2(configuration, cfg, true);
        bucketManager = new BucketManager(auth, cfg);
        uploadManager = new UploadManager(cfg);
    }

    @Override
    public List<JBucket> listBuckets() throws JFOSException {
        try {
            String[] names = bucketManager.buckets();
            List<JBucket> jBuckets = new ArrayList<JBucket>(names.length);
            for (String name : names) {
                jBuckets.add(new JKodoBucket(name));
            }
            return jBuckets;
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JBucket createBucket(String bucketName) throws JFOSException {
        try {
            bucketManager.createBucket(bucketName, configuration.getRegion().getRegion());
            return new JKodoBucket(bucketName);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void deleteBucket(String bucketName) throws JFOSException {
        try {
            Field configHelperField = BucketManager.class.getDeclaredField("configHelper");
            Object configHelper = JObjectUtils.getFieldValue(configHelperField, bucketManager);
            Method rsHostMethod = configHelperField.getType().getMethod("rsHost");
            String url = String.format("%s/drop/%s", JObjectUtils.invokeMethod(rsHostMethod, configHelper), bucketName);
            Method postMethod = BucketManager.class.getDeclaredMethod("post", String.class, byte[].class);
            Response res = (Response) JObjectUtils.invokeMethod(postMethod, bucketManager, url, null);
            if (!res.isOK()) {
                throw new QiniuException(res);
            }
        } catch (Exception e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public boolean doesBucketExist(String bucketName) throws JFOSException {
        try {
            return Arrays.stream(bucketManager.buckets()).anyMatch(item -> bucketName.equals(item));
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObjectListing listObjects(String bucketName) throws JFOSException {
        try {
            FileListing listing = bucketManager.listFiles(bucketName, null, null,
                    JListObjectsRequest.DEFAULT_RETURNED_KEYS_LIMIT, null);
            return new JKodoObjectListing(listing, bucketName, null, null,
                    JListObjectsRequest.DEFAULT_RETURNED_KEYS_LIMIT, null);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObjectListing listObjects(String bucketName, String prefix) throws JFOSException {
        try {
            FileListing listing = bucketManager.listFiles(bucketName, prefix, null,
                    JListObjectsRequest.DEFAULT_RETURNED_KEYS_LIMIT, null);
            return new JKodoObjectListing(listing, bucketName, prefix, null,
                    JListObjectsRequest.DEFAULT_RETURNED_KEYS_LIMIT, null);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObjectListing listObjects(JListObjectsRequest request) throws JFOSException {
        try {
            FileListing listing = bucketManager.listFiles(request.getBucketName(), request.getPrefix(),
                    request.getMarker(), request.getMaxKeys(), request.getDelimiter());
            return new JKodoObjectListing(listing, request.getBucketName(), request.getPrefix(), request.getMarker(),
                    request.getMaxKeys(), request.getDelimiter());
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObjectMetadata getObjectMetadata(String bucketName, String key) throws JFOSException {
        try {
            return new JKodoObjectMetadata(bucketManager.stat(bucketName, key));
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void deleteObject(JObjectSummary summary) throws JFOSException {
        try {
            bucketManager.delete(summary.getBucketName(), summary.getKey());
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void deleteObject(String bucketName, String key) throws JFOSException {
        try {
            bucketManager.delete(bucketName, key);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public List<String> deleteObjects(String bucketName, List<String> keys) throws JFOSException {
        int length = keys.size();
        try {
            BucketManager.BatchOperations batchOperations = new BucketManager.BatchOperations();
            batchOperations.addDeleteOp(bucketName, keys.toArray(new String[0]));
            Response response = bucketManager.batch(batchOperations);
            BatchStatus[] batchStatusList = response.jsonToObject(BatchStatus[].class);
            List<String> result = null;
            for (int i = 0; i < length; i++) {
                BatchStatus status = batchStatusList[i];
                if (status.code != 200) {
                    if (result == null) {
                        result = new ArrayList<String>();
                    }
                    result.add(keys.get(i));
                }
            }
            return result == null ? Collections.emptyList() : result;
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void copyObject(String sourceBucketName, String sourceKey, String destinationBucketName,
            String destinationKey) throws JFOSException {
        try {
            bucketManager.copy(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public JObject getObject(String bucketName, String key) throws JFOSException {
        URL url = null;
        try {
            url = new URL(generateUrl(bucketName, key, 600));
        } catch (IOException e) {
            throw new JFOSException(e.getMessage(), e);
        }
        return new JKodoObject(url, bucketName, key, this);
    }

    @Override
    public JObjectMetadata getObject(String bucketName, String key, File file) throws JFOSException {
        InputStream in = null;
        OutputStream outputStream = null;
        try {
            URL url = new URL(generateUrl(bucketName, key, 600));
            in = url.openStream();
            outputStream = new FileOutputStream(file);
            JObjectUtils.crossStream(in, outputStream);
            return new JKodoObjectMetadata(bucketManager.stat(bucketName, key));
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        } catch (IOException e) {
            throw new JFOSException(e.getMessage(), e);
        } finally {
            JObjectUtils.close(outputStream);
            JObjectUtils.close(in);
        }
    }

    @Override
    public void putObject(String bucketName, String key, InputStream input) throws JFOSException {
        try {
            uploadManager.put(input, key, auth.uploadToken(bucketName), null, null);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void putObject(String bucketName, String key, InputStream input, String contentType) throws JFOSException {
        try {
            uploadManager.put(input, key, auth.uploadToken(bucketName), null, contentType);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void putObject(String bucketName, String key, File file) throws JFOSException {
        try {
            uploadManager.put(file, key, auth.uploadToken(bucketName));
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public void putObject(String bucketName, String key, File file, String contentType) throws JFOSException {
        try {
            uploadManager.put(file, key, auth.uploadToken(bucketName), null, contentType, false);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
    }

    @Override
    public String generateUrl(String bucketName, String key, int expires) throws JFOSException {
        String encodedFileName = null;
        try {
            encodedFileName = URLEncoder.encode(key, "utf-8").replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
        }
        String domain = null;
        try {
            domain = getDomain(bucketName);
        } catch (QiniuException e) {
            throw new JFOSException(e.getMessage(), e);
        }
        if (domain == null) {
            throw new JFOSException("no such bucket domain");
        }
        String publicUrl = String.format("%s/%s", domain, encodedFileName);
        return auth.privateDownloadUrl(publicUrl, expires);
    }

    private String getDomain(String bucketName) throws QiniuException {
        String domain = this.configuration.getDomainOfBucket().get(bucketName);
        if (domain == null) {
            String[] domains = bucketManager.domainList(bucketName);
            if (domains != null && domains.length > 0) {
                return this.configuration.getHttpType().getProtocol() + "://" + domains[0];
            }
        }
        return null;
    }

    @Override
    public void destory() {
    }

}
